//-----------------------------------------------------------------------------
//
// @file	Scene.cpp
// @brief	File with all scene data and functions
// @author	Vinod Melapudi
// @date	20Feb2010
//
//-----------------------------------------------------------------------------

#include <stdio.h>
#include <time.h>
#include <float.h>

#include "OpenGLRender.h"

#include "Scene.h"
#include "Renderer.h"
#include "Camera.h"
#include "Transform.h"


using namespace std;

// renderer initialized in main file
extern Renderer *g_renderer;

//-----------------------------------------------------------------------------


//
// @brief	Init scene
//
bool Scene::Init()
{
	// Initialize attributes
	m_show_HUD = true;

	m_cameraEnableNavigation = true;
/*
	// Init buffers
	if ( !InitTextures() )
		return false;

#ifdef VBO_SUPPORT
	if ( !InitVBO() )
		return false;
#endif
*/


	return true;
}

//
// @brief	UnInit buffers
//
void Scene::UnInit()
{
/*
	// Buffers
	UnInitTextures();

#ifdef VBO_SUPPORT
	UnInitVBO();
#endif

#ifdef SHADER_SUPPORT
	g_renderer->UnInitShader( m_vertex_shader, m_fragment_shader, m_shader_program );
#endif
*/
}

//
// @brief	Scene process
//
void Scene::Update()
{
	// FPS
	static long frameNum = 0;
	static clock_t lastClock = clock();	
	static float fps = 0.f;
	{
		frameNum++;
		clock_t currClock = clock();
		if ( currClock - lastClock > CLOCKS_PER_SEC )
		{
			fps = frameNum * CLOCKS_PER_SEC / (float)(currClock - lastClock);
			frameNum = 0;
			lastClock = currClock;
		}
	}
	m_FPS = fps;
}

//
// @brief	Scene render
//
void Scene::Render( Renderer* renderer )
{
}

//
// @brief	Render HUD
//
void Scene::RenderHUD( Renderer* renderer, float normalized_x_pos, float normalized_y_pos, vector<string> text )
{
	glColor3f( 1.f, 1.f, 1.f );

	glMatrixMode( GL_PROJECTION );
	glPushMatrix();
	
    glLoadIdentity();
	glOrtho(-1, 1, -1, 1, 0, 1);
	
	glMatrixMode( GL_MODELVIEW );
	glPushMatrix();
    glLoadIdentity();

	float spacing = 0.04f;

	// Render FPS
	char buf[64] = "";
	sprintf( buf, "Frames/Second : %5.2f", m_FPS );
	renderer->RenderText( buf, normalized_x_pos, normalized_y_pos-=spacing );
	renderer->RenderText( "", normalized_x_pos, normalized_y_pos-=spacing );

	// Render Camera nav help
	if ( m_cameraEnableNavigation )
	{
		renderer->RenderText( "Camera Navigation",				normalized_x_pos, normalized_y_pos-=spacing );
		renderer->RenderText( "   LeftMouse+Ctrl : Rotate",		normalized_x_pos, normalized_y_pos-=spacing );
		renderer->RenderText( "   LeftMouse+Shift : Pan",		normalized_x_pos, normalized_y_pos-=spacing );
		renderer->RenderText( "   LeftMouse+Ctrl+Shift : Zoom", normalized_x_pos, normalized_y_pos-=spacing );
		renderer->RenderText( "   F : Focus",					normalized_x_pos, normalized_y_pos-=spacing );
		renderer->RenderText( "",								normalized_x_pos, normalized_y_pos-=spacing );
	}

	// Render scene texts
	for( vector<string>::const_iterator iter=text.begin(); iter != text.end(); iter++ )
		renderer->RenderText( iter->c_str(), normalized_x_pos, normalized_y_pos-=spacing );

	glPopMatrix();

	glMatrixMode( GL_PROJECTION );
	glPopMatrix();
}

//
// @brief	Render text in scene
//
void Scene::RenderText( Renderer* renderer, const SceneText& scene_text )
{
	renderer->RenderText( scene_text.text.c_str(), scene_text.normalized_x_pos, scene_text.normalized_y_pos );
}


//
// @brief	Render texts in scene
//
void Scene::RenderText( Renderer* renderer, vector<SceneText> scene_texts )
{
	glMatrixMode( GL_PROJECTION );
	glPushMatrix();
	
    glLoadIdentity();
	glOrtho(-1, 1, -1, 1, 0, 1);
	
	glMatrixMode( GL_MODELVIEW );
	glPushMatrix();
    glLoadIdentity();

	for( vector<SceneText>::const_iterator iter=scene_texts.begin(); iter != scene_texts.end(); iter++ )
		renderer->RenderText( iter->text.c_str(), iter->normalized_x_pos, iter->normalized_y_pos );

	glPopMatrix();

	glMatrixMode( GL_PROJECTION );
	glPopMatrix();
}

//
// @brief	Handle keys/mouse : Returns true if message was handled
//
bool Scene::HandleMessage( UINT msg, WPARAM wParam, LPARAM lParam )
{
	m_mouse_pos = GetMousePos( lParam );

	switch( msg )
	{
	case WM_LBUTTONDOWN:
		m_mouse_last_pos = m_mouse_pos;
		break;

	case WM_LBUTTONUP:
		break;

	case WM_MOUSEMOVE:
	{
		Vector2 mouseDiff = m_mouse_pos - m_mouse_last_pos;
		m_mouse_last_pos = m_mouse_pos;

		if ( !m_cameraEnableNavigation || mouseDiff.norm() < FLT_EPSILON )
			break;
	
		if ( wParam == ( MK_CONTROL | MK_LBUTTON ) )
		{
			// Rotate Cam
			m_camera.Rotate( mouseDiff.x, mouseDiff.y );
		}
		else if ( wParam == ( MK_CONTROL | MK_SHIFT | MK_LBUTTON ) )
		{
			//Zoom Cam
			m_camera.Zoom( mouseDiff.x );
		}
		else if ( wParam == ( MK_SHIFT | MK_LBUTTON ) )
		{
			// Pan Cam
			m_camera.Pan( mouseDiff.x, mouseDiff.y );
		}
	}
	break;

	case WM_KEYDOWN:
		switch( wParam )
		{	
			case 'F':
				if ( m_cameraEnableNavigation )
				{
					// Look at origin
					//m_cameraPos = m_cameraPos - m_look_at;
					m_camera.m_look_at.set( 0, 0, 0 );
				}
				break;
			default:
				return false;
				break;
		}
	default:
		return false;
		break;
	};

	return true;
}

void Scene::SetScreenDimension( int w, int h )
{
	m_screen_width = w;
	m_screen_height = h;
}

void Scene::GetScreenDimension( int &w, int &h )
{
	w = m_screen_width;
	h = m_screen_height;
}


//
// @brief	Helper function to get mouse position from param
//
Vector2 Scene::GetMousePos( LPARAM lParam ) const
{
	int xPos = GET_X_LPARAM(lParam); 
	int yPos = GET_Y_LPARAM(lParam);
	return Vector2( xPos - m_screen_width/2, m_screen_height/2 - yPos );
}

// Draw axis at given point
void Scene::DrawAxis( Vector3 position, float size ) const
{
	Vector3 xAxis = position + Vector3(size,0.f,0.f);
	Vector3 yAxis = position + Vector3(0.f,size,0.f);
	Vector3 zAxis = position + Vector3(0.f,0.f,size);

	glBegin( GL_LINES );
		glColor3f( 1, 0, 0 );
		glVertex3f( position.x, position.y, position.z );
		glVertex3f( xAxis.x, xAxis.y, xAxis.z );
		glColor3f( 0, 1, 0 );
		glVertex3f( position.x, position.y, position.z );
		glVertex3f( yAxis.x, yAxis.y, yAxis.z );
		glColor3f( 0, 0, 1 );
		glVertex3f( position.x, position.y, position.z );
		glVertex3f( zAxis.x, zAxis.y, zAxis.z );
	glEnd();
}

// Get screen to world pos at near and far plane
void Scene::ScreenToWorld( Vector2 spos, Vector3& near_wpos, Vector3& far_wpos ) const
{
	// Scale position to normalized coords
	spos.set( spos.x / (m_screen_width/2.f), spos.y / (m_screen_height/2.f) );

	// Get gl matrix
	float projection_gl_mat[16], modelview_gl_mat[16];
	Transform projection_xform, modelview_xform;

	glMatrixMode( GL_PROJECTION );
	glGetFloatv( GL_PROJECTION_MATRIX, projection_gl_mat );
	projection_xform.FromGLMatrix( projection_gl_mat );

	glMatrixMode( GL_MODELVIEW );
	glGetFloatv( GL_MODELVIEW_MATRIX, modelview_gl_mat );
	modelview_xform.FromGLMatrix( modelview_gl_mat );
	
	// Get view coords from screen coords
	Vector3 view_coords = projection_xform.inv() * Vector3( spos.x, spos.y, 0.f );

	// Get view coords at near plane
	Vector3 near_view_coords = view_coords * ( -m_camera.m_near_z / view_coords.z );
	near_wpos = modelview_xform.inv() * near_view_coords;

	// Get view coords at far plane
	Vector3 far_view_coords = view_coords * ( -m_camera.m_far_z / view_coords.z );
	far_wpos = modelview_xform.inv() * far_view_coords;
}

// Get world to screen pos
bool Scene::WorldToScreen( Vector3 wpos, Vector2 &spos ) const
{
	// Get gl matrix
	float projection_gl_mat[16], modelview_gl_mat[16];
	Transform projection_xform, modelview_xform;

	glMatrixMode( GL_PROJECTION );
	glGetFloatv( GL_PROJECTION_MATRIX, projection_gl_mat );
	projection_xform.FromGLMatrix( projection_gl_mat );

	glMatrixMode( GL_MODELVIEW );
	glGetFloatv( GL_MODELVIEW_MATRIX, modelview_gl_mat );
	modelview_xform.FromGLMatrix( modelview_gl_mat );

	// Convert world to normalized coords
	Vector3 view_coords = modelview_xform * wpos;
	Vector3 clip_coords = projection_xform * view_coords;
	Vector3 normal_coords = clip_coords / clip_coords.z;

	spos.set( normal_coords.x*(m_screen_width/2.f), normal_coords.y*(m_screen_height/2.f) );

	return ( normal_coords.x > -1 && normal_coords.x < 1 && normal_coords.y > -1 && normal_coords.y < 1 && clip_coords.z > 1 );
}


#ifdef VBO_SUPPORT
//
// @brief	Load all VBO
//
bool Scene::InitVBO()
{
	m_vertex_VBO = g_renderer->LoadVBO( CUBENUMVERTICES*3, cubeVertices );
	m_normal_VBO = g_renderer->LoadVBO( CUBENUMVERTICES*3, cubeNormals );
	m_texture_VBO = g_renderer->LoadVBO( CUBENUMVERTICES*2, cubeTexCoords );
	return true;
}

//
// @brief	Uninit VBO
//
void Scene::UnInitVBO()
{
	g_renderer->UnInitVBO( &m_vertex_VBO );
	g_renderer->UnInitVBO( &m_normal_VBO );
	g_renderer->UnInitVBO( &m_texture_VBO );
}
#endif



